package com.common.filter;

import cn.hutool.core.io.IoUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.common.exception.LoginBodyEmptyAuthenticationException;
import com.common.exception.LoginGrantTypeEmptyAuthenticationException;
import com.common.exception.LoginOauth2AuthenticationException;
import com.common.exception.LoginOauth2ModeFactoryEmptyAuthenticationException;
import com.common.factory.Oauth2AuthenticationFactory;
import com.common.handler.SystemDefaultAuthenticationFailureHandler;
import com.common.handler.SystemDefaultAuthenticationSuccessHandler;
import com.common.matcher.SystemDefaultLoginRequestMatcher;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 系统默认的登陆过滤器
 */
@Component
public class SystemDefaultLoginFilter extends OncePerRequestFilter {

    private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
            .getContextHolderStrategy();

    private SecurityContextRepository securityContextRepository = new RequestAttributeSecurityContextRepository();

    @Autowired(required = false)
    private RequestMatcher loginRequestMatcher = new SystemDefaultLoginRequestMatcher();

    @Autowired(required = false)
    private List<Oauth2AuthenticationFactory> oauth2AuthenticationFactoryList;

    @Autowired(required = false)
    private AuthenticationSuccessHandler successHandler = new SystemDefaultAuthenticationSuccessHandler();

    @Autowired(required = false)
    private AuthenticationFailureHandler failureHandler = new SystemDefaultAuthenticationFailureHandler();


    @Override
    protected void doFilterInternal(HttpServletRequest servletRequest, HttpServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
        // 判断当前是否为登陆接口
        if (!loginRequestMatcher.matches(servletRequest)) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        // 进行登陆的认证，并返回对应的认证响应信息
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ServletInputStream inputStream = servletRequest.getInputStream();) {
            IoUtil.copy(inputStream, baos);
            // 获取请求体信息
            String body = IoUtil.toStr(baos, StandardCharsets.UTF_8);
            if (!StringUtils.hasText(body)) {
                throw new LoginBodyEmptyAuthenticationException("认证失败，请求体信息为空");
            }
            JSONObject bodyJsonObject = JSONUtil.parseObj(body);
            String grantType = bodyJsonObject.getStr("grantType");
            if (!StringUtils.hasText(grantType)) {
                throw new LoginGrantTypeEmptyAuthenticationException("认证失败，身份认证类型为空");
            }
            if (CollectionUtils.isEmpty(oauth2AuthenticationFactoryList)) {
                throw new LoginOauth2ModeFactoryEmptyAuthenticationException("认证失败，Oauth2模型工厂为空");
            }
            Authentication authentication = null;
            for (Oauth2AuthenticationFactory oauth2AuthenticationFactory : oauth2AuthenticationFactoryList) {
                if (oauth2AuthenticationFactory.support(grantType)) {
                    authentication = oauth2AuthenticationFactory.authentication(bodyJsonObject);
                    break;
                }
            }
            if (ObjectUtils.isEmpty(authentication)) {
                throw new LoginOauth2AuthenticationException("登陆失败");
            }
            successfulAuthentication(servletRequest, servletResponse, authentication);
        } catch (AuthenticationException e) {
            // 认证失败，走失败的处理器
            failureHandler.onAuthenticationFailure(servletRequest, servletResponse, e);
        }
    }

    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws ServletException, IOException {
        SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
        context.setAuthentication(authResult);
        this.securityContextHolderStrategy.setContext(context);
        this.securityContextRepository.saveContext(context, request, response);
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }
}
